#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <math.h>
#include <time.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <pthread.h>
#include <inttypes.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <getopt.h>
#include <unistd.h>
#include <curl/curl.h>

#include "hls.h"

#define CURL_USER_AGENT_STR 				"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36"

#define HLS_START_MARKER					"#EXTM3U"
#define HLS_KEY_MARKER						"#EXT-X-KEY:"
#define HLS_STREAM_MARKER					"#EXT-X-STREAM-INF:"
#define HLS_DATETIME_MARKER					"#EXT-X-PROGRAM-DATE-TIME:"
#define HLS_TARGET_DUR_MARKER				"#EXT-X-TARGETDURATION:"
#define HLS_MEDIA_SEQ_MARKER				"#EXT-X-MEDIA-SEQUENCE:"
#define HLS_VERSION_MARKER					"#EXT-X-VERSION:"

#define HLS_SEGMENT_LEN_MARKER				"#EXTINF:"
#define HLS_END_MARKER						"#EXT-X-ENDLIST"

#define HLS_STREAM_PRGID_MARKER				"PROGRAM-ID"
#define HLS_STREAM_BANDWIDTH_MARKER			"BANDWIDTH"
#define HLS_BANDWIDTH_MARKER				"BANDWIDTH="

#define HLS_KEY_METHOD_MARKER				"METHOD="
#define HLS_KEY_URI_MARKER					"URI="
#define HLS_KEY_IV_MARKER					"IV="
#define HLS_AES128_STR						"AES-128"

#define HLS_DEFAULT_BUFSIZE					(562500 * 20)

void _hls_swap_streams_in_list(HLS_STREAM_URL *, HLS_STREAM_URL *);
int _hls_process_master_playlist(HLS_MASTER_URL *);
int _hls_reload_master_url(HLS_MASTER_URL *);

void _hls_key_destroy(HLS_KEY *);
void _hls_segment_destroy(HLS_SEGMENT *);
void _hls_stream_destroy(HLS_STREAM_URL *);
int _hls_fetch_segment(HLS_SEGMENT *);
int _hls_decrypt_segment(HLS_SEGMENT *);
int _hls_process_stream_playlist(HLS_STREAM_URL *);
void _hls_print_key(HLS_KEY *);
UTILS_DATAPAIR_STRUCT * _hls_fetch_url(char *, char *);



void hls_set_max_bitrate(HLS_MASTER_URL * master, uint64_t speed)
{
	if (master)
		master->max_bandwidth = speed;
	return;
}

void hls_set_min_bitrate(HLS_MASTER_URL * master, uint64_t speed)
{
	if (master)
		master->min_bandwidth = speed;
	return;
}

void hls_set_adaptive(HLS_MASTER_URL * master, uint8_t adapt)
{
	if (master)
		master->adaptive = adapt;
	return;
}
int hls_set_bitrate(HLS_MASTER_URL * master, uint64_t speed)
{
	int ret = 0;
	if (master && speed)
	{
		int i;

		if (master->active_stream && master->active_stream->bandwidth == speed)
		{
			printf("[HLS] hls_set_bitrate, active and target are the same: %" PRId64"\n", speed);
			return ret;
		}

		if (speed >= master->streams[master->stream_count-1].bandwidth)
		{
			printf("[HLS] hls_set_bitrate, goto highest: %" PRId64"\n", speed);
			master->active_stream = &master->streams[master->stream_count-1];
			return 1;
		}

		for(i=0; i < master->stream_count - 1; i++)
		{
			printf("[HLS] Compare : %" PRId64" (%" PRId64")\n", master->streams[i].bandwidth, speed);
			if (speed >= master->streams[i].bandwidth &&
				speed < master->streams[i+1].bandwidth)
			{
				printf("[HLS] Setting start bandwidth to: %" PRId64"\n", master->streams[i].bandwidth);
//				master->segment_fetch_index = master->active_stream->active_segment->index;
				master->active_stream = &master->streams[i];
				ret = 1;
				break;
			}
			else if (speed == master->streams[i+1].bandwidth)
			{
//				printf("Fehio\n");
				master->active_stream = &master->streams[i+1];
				ret = 1;
				break;
			}
		}
	}
	return ret;
}

int hls_switch_bitrate_up(HLS_MASTER_URL * master, uint64_t bitrate)
{
	int ret = 0;
	if (master)
	{
		if (master->active_stream->index < master->stream_count-1)
		{
//			master->segment_fetch_index = master->active_stream->active_segment->index;
			if (bitrate == 0)
			{
				master->active_stream = &master->streams[master->active_stream->index+1];
				printf("[MLB] hls_switch_bitrate_up, 1\n");
				ret = 1;
			}
			else
			{
				ret = hls_set_bitrate(master, bitrate);
			}
		}

		if (ret == 1)
			hls_update_stream_playlist(master);
	}
	return ret;
}

int hls_switch_bitrate_down(HLS_MASTER_URL * master, uint64_t bitrate)
{
	int ret = 0;
	if (master)
	{
		if (master->active_stream->index > 0)
		{
//			master->segment_fetch_index = master->active_stream->active_segment->index;
			if (bitrate == 0)
			{
				printf("[MLB] hls_switch_bitrate_down, 1\n");
				master->active_stream = &master->streams[master->active_stream->index-1];
				ret = 1;
			}
			else
			{
				ret = hls_set_bitrate(master, bitrate);
			}
		}
		if (ret == 1)
			hls_update_stream_playlist(master);

	}
	return ret;
}


uint64_t hls_get_playlist_start_time(HLS_STREAM_URL *stream)
{
	return 0;
}

uint64_t hls_get_playlist_end_time(HLS_STREAM_URL *stream)
{
	return 0;
}

HLS_SEGMENT * hls_stream_fetch_decrypt(HLS_STREAM_URL *stream)
{
	HLS_SEGMENT * ret = NULL;
//	printf("hls_stream_fetch_decrypt\n");
	if (stream && stream->parent)
	{
		HLS_MASTER_URL *master = stream->parent;

		if (stream->state == HLS_STATE_LIVE)
		{
			if (master->active_stream->ext_x_target_duration)
				hls_update_stream_playlist(master);
/*
			if (!stream->active_segment && hls_get_stream_segment_count(stream) < 5)
//			if (!stream->active_segment)
			{
				printf("[HLS] Setting active segment to first!!!!!!\n");
				stream->active_segment = stream->first_segment;
			}
*/
		}

		if (stream->active_segment)
		{
			uint64_t start, end;
			int seg_ret = 1, count = 0;
			
			start = get_time_ms();
			while(seg_ret == 1)
			{
				seg_ret = _hls_fetch_segment(stream->active_segment);
				if (seg_ret == 1)
				{
					count++;
					printf("[HLS] RETRY: %d\n", count);
					if (count > 4)
					{
						printf("[HLS] tired of RETRY, exit (%d)\n", count);
						return NULL;
					}
				}
			}

			_hls_decrypt_segment(stream->active_segment);
			end = get_time_ms();

			stream->active_segment->process_time = end - start;
			printf("[HLS] Total Time: %" PRId64"\n", stream->active_segment->process_time);
			ret = stream->active_segment;

			if (master->adaptive)
			{
				double _speed = ((((double)ret->data.data_size)/((double)stream->active_segment->process_time/1000.0)) * 8);
				//printf("speed: %0.2f -- [%d/%" PRId64"] (%" PRId64")\n",  _speed, ret->data.data_size, stream->active_segment->process_time, stream->bandwidth);
				if (_speed > stream->bandwidth)
				{
					if (hls_switch_bitrate_up(master, floor(_speed)) == 1)
					{
						printf("[HLS]\tAdaptive, switch up: %0.2fMB (%0.2fMB)\n", _speed/1000000, (double)stream->bandwidth/1000000.0);
					}
				}
				else if (_speed < stream->bandwidth)
				{
					if (hls_switch_bitrate_down(master, floor(_speed)) == 1)
					{
						printf("[HLS]\tAdaptive, switch down: %0.2fMB (%0.2fMB)\n", _speed/1000000, (double)stream->bandwidth/1000000.0);
					}
				}
			}

			start = get_time_ms();
			while(stream->state == HLS_STATE_LIVE && stream->active_segment->next == NULL)
			{
//				printf("[HLS] hls_stream_fetch_decrypt, next segment would be null and state is live, refresh\n");

				if (hls_update_stream_playlist(master) == 1)
					usleep(floor(master->active_stream->ext_x_target_duration) * 1000 * 1000);

				end = get_time_ms();
				if ((end - start)/1000 > (master->active_stream->ext_x_target_duration * 4))
				{
					printf("[HLS] Playlist did not update in time, stopping\n");
					stream->state = HLS_STATE_END;
					return NULL;
				}
			}
			stream->active_segment = stream->active_segment->next;
//			printf("[HLS] NExt Fetch: %s (%" PRId64")\n", stream->active_segment->url, stream->active_segment->index);

			if(stream->active_segment)
				master->segment_fetch_index = stream->active_segment->index;
			else
				printf("[HLS] hls_stream_fetch_decrypt, no active segment [1]\n");
		}
		else
		{
			printf("[HLS] hls_stream_fetch_decrypt, no active segment [2]\n");
		}
	}
	return ret;
}

int hls_update_stream_playlist(HLS_MASTER_URL * master)
{
	int ret = 0;
	if (master)
	{
		uint64_t now = (uint64_t)time(NULL);

		if (now - master->active_stream->last_update_time > master->active_stream->ext_x_target_duration)
		{
			master->active_stream->last_update_time = (uint64_t)time(NULL);
			ret = _hls_process_stream_playlist(master->active_stream);
		}
		else
		{
			ret = 1;
		}

		if (ret == 0 && master->active_stream)
		{
//			printf("Setting Active segment idx to: %" PRId64"\n", master->segment_fetch_index);
			hls_set_segment_index(master->active_stream, master->segment_fetch_index);
		}
	}
	return ret;
}



void hls_print_stream_info(HLS_MASTER_URL * master)
{
	if (master)
	{
		int i;
		printf("[HLS] Setting Master URL to: %s\n", master->master_url);
		printf("[HLS] Setting Base URL to: %s\n", master->base_url);
//		printf("[HLS] Setting URL Params: %s\n", master->url_params);

		for(i=0; i < master->stream_count; i++)
		{
			printf("[%d] PgmID: %d, B: %" PRId64", Url: %s (%s)\n", i, master->streams[i].program_id, master->streams[i].bandwidth, master->streams[i].url, master->streams[i].base_url);
		}
	}
}

void _hls_print_key(HLS_KEY *key)
{
	if (key)
	{
		int i;
		printf("AES:, Key: 0x");
		for(i=0; i < AES128_KEY_SIZE; i++)
			printf("%02X", key->key[i]);
		printf(" IV: 0x");
		for(i=0; i < AES128_KEY_SIZE; i++)
			printf("%02X", key->iv[i]);
		printf("\n");
	}
}

HLS_SEGMENT * hls_set_segment_live(HLS_STREAM_URL *stream)
{
	HLS_SEGMENT * ret = NULL;
	if (stream)
	{
		HLS_MASTER_URL *master = stream->parent;
		uint64_t seg_count = hls_get_stream_segment_count(stream);
		if (seg_count <= HLS_SEGMENT_LIVE_MINUS)
			seg_count = HLS_SEGMENT_LIVE_MINUS;

		ret = hls_set_segment_index(stream, seg_count - HLS_SEGMENT_LIVE_MINUS);

		if (master)
			master->segment_fetch_index = stream->active_segment->index;

	}
	return ret;

}

HLS_SEGMENT * hls_set_segment_time(HLS_STREAM_URL *stream, double timecode)
{
	HLS_SEGMENT * ret = NULL;
	if (stream && stream->parent && stream->active_segment && stream->first_segment)
	{
		HLS_MASTER_URL *master = stream->parent;

		if (timecode <= stream->first_segment->timecode)
		{
//			printf("hls_set_segment_time 1: %0.2f .. %0.2f\n", time, ret->timecode);
			stream->active_segment = stream->first_segment;
		}
		else if (timecode > stream->curent_segment->timecode)
		{
			stream->active_segment = stream->curent_segment;
		}
		else
		{
			ret = stream->first_segment;
			while (ret && ret->next)
			{
//				printf("hls_set_segment_time 1a: %0.2f .. %0.2f\n", time, ret->timecode);
				if (timecode >= ret->timecode  && timecode < ret->next->timecode)
				{
					printf("hls_set_segment_time 2\n");
					stream->active_segment = ret->next;
					break;
				}
				ret = ret->next;
			}
		}

		if (master)
			master->segment_fetch_index = stream->active_segment->index;

	}
	return ret;
}

HLS_SEGMENT * hls_set_segment_index(HLS_STREAM_URL *stream, uint64_t index)
{
	HLS_SEGMENT * ret = NULL;
	if (stream)
	{
		HLS_MASTER_URL *master = stream->parent;
		ret = stream->first_segment;
		while (ret)
		{
			if (ret->index == index)
			{
//				printf("hls_set_segment_index 1\n");
				stream->active_segment = ret;
				break;
			}
			ret = ret->next;
		}
		if (master)
			master->segment_fetch_index = stream->active_segment->index;
	}
	return stream->active_segment;
}

int _hls_decrypt_segment(HLS_SEGMENT *segment)
{
	int ret = 0;

	if (segment && segment->parent)
	{
		HLS_STREAM_URL *stream = segment->parent;
		if (stream && stream->parent)
		{
			HLS_MASTER_URL *master = stream->parent;
			if (segment->data.data && segment->data.data_size)
			{
//				printf("RetSz: %d\n", segment->data.data_size);
				if (segment->key && segment->key->type == HLS_KEY_TYPE_AES128)
				{
					int lastDecryptLength = 0, dec_len = 0;
					HLS_KEY *key = segment->key;
					EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
	                EVP_CIPHER_CTX_init(ctx);

//					_hls_print_key(key);
//					printf("[HLS] Decrypt segment with AES-128 [CBC]\n");
					if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key->key, key->iv) == 1)
					{
//						printf("[HLS] EVP_DecryptInit_ex was good\n");
						if (EVP_DecryptUpdate(ctx, master->decrypt_mem, &dec_len, segment->data.data, segment->data.data_size) == 1)
						{
//							printf("[HLS] EVP_DecryptUpdate was good\n");
							if (EVP_DecryptFinal_ex(ctx, master->decrypt_mem + dec_len, &lastDecryptLength) == 1)
							{
//								printf("[HLS] EVP_DecryptFinal_ex was good\n");
								master->decrypt_mem_ret = ret = dec_len + lastDecryptLength;
//								printf("IN: %d, OUT: %d\n",  segment->data.data_size, master->decrypt_mem_ret);
							}
							else
							{
								printf("[HLS] EVP_DecryptFinal_ex was bad\n");
							}
						}
						else
						{
							printf("[HLS] EVP_DecryptUpdate was bad\n");
						}
					}
					else
						printf("[HLS] EVP_DecryptInit_ex was bad\n");

					EVP_CIPHER_CTX_cleanup(ctx);
				}

				free(segment->data.data);
				segment->data.data_size = 0;
				if (master->cb_post_decrypt)
					(*master->cb_post_decrypt)(segment);

			}
		}
	}
	return ret;
}

uint8_t hls_get_stream_state(HLS_STREAM_URL * stream)
{
	if (stream)
	{
		return stream->state;
	}
	return 0;
}

uint64_t hls_get_stream_start_datetime(HLS_STREAM_URL * stream)
{
	if (stream)
	{
		return stream->start_time;
	}
	return 0;
}

uint64_t hls_get_stream_segment_count(HLS_STREAM_URL * stream)
{
	if (stream)
	{
		return stream->segment_count;
	}
	return 0;
}

int _hls_fetch_segment(HLS_SEGMENT *segment)
{
	int ret = 0;
	if (segment && segment->parent)
	{
		HLS_STREAM_URL *stream = segment->parent;
		if (stream && stream->parent)
		{
			char tmpstr[MAX_STR_LEN*2] = {0};
			UTILS_DATAPAIR_STRUCT * seg_data = NULL;
			HLS_MASTER_URL *master = stream->parent;
			HLS_FETCH_OPTIONS  f_opts = {0};

			f_opts.errbuf = malloc(CURL_ERROR_SIZE);
			f_opts.timeout = ceil(segment->duration * 1.5);
			f_opts.connect_timeout = 3;
			snprintf(tmpstr, sizeof(tmpstr)-1, "%s/%s/%s",  master->base_url, stream->base_url, segment->url);

			printf("[HLS] Fetching [%d]: %s (%" PRId64")\n",  f_opts.timeout, tmpstr, segment->index);
//			_hls_print_key(segment->key);

			seg_data = hls_curl_get(tmpstr, &f_opts);
			if (seg_data)
			{
				memcpy(&segment->data, seg_data, sizeof(UTILS_DATAPAIR_STRUCT));
				free(seg_data);
/*
				printf("[HLS] Fetched block good: 0x%x 0x%x 0x%x 0x%x\n", segment->data.data[0],
																		segment->data.data[1],
																		segment->data.data[2],
																		segment->data.data[3]);
*/
				printf("[HLS] Fetching done\n");
			}
			else
			{
				if (f_opts.errbuf)
				{
					printf("[HLS] Error fetching: %s\n", f_opts.errbuf);
					ret = 1;
				}
			}
			curl_easy_cleanup(master->segment_opts.handle);

			if (f_opts.errbuf)
				free(f_opts.errbuf);

			if (master->cb_post_fetch)
				(*master->cb_post_fetch)(segment);
		}
//		return 1;
	}
	return ret;
}


void _hls_swap_streams_in_list(HLS_STREAM_URL *a, HLS_STREAM_URL *b)
{
	if (a && b)
	{
		HLS_STREAM_URL tmp;
		memcpy(&tmp, a, sizeof(HLS_STREAM_URL));
		memcpy(a, b, sizeof(HLS_STREAM_URL));
		memcpy(b, &tmp, sizeof(HLS_STREAM_URL));
	}
}

int _hls_process_stream_playlist(HLS_STREAM_URL *stream)
{
	if (stream && stream->parent)
	{
		HLS_MASTER_URL * master = stream->parent;
		UTILS_DATAPAIR_STRUCT *tmp_data = NULL;
		char tmpstr[MAX_STR_LEN] = {0};
		uint32_t old_pos = 0;

		snprintf(tmpstr, MAX_STR_LEN-1, "%s/%s", master->base_url, stream->url);
		old_pos = stream->playlist_position;

		tmp_data = hls_curl_get(tmpstr, &master->playlist_opts);

		if (tmp_data && tmp_data->data_size > stream->playlist_mem.data_size)
		{
			char * line = NULL;
//			printf("[HLS] Process: %s ... (pos: %d, old: %d)\n", tmpstr, stream->playlist_position, old_pos);

			if (stream->playlist_mem.data)
				free(stream->playlist_mem.data);
			memcpy(&stream->playlist_mem, tmp_data, sizeof(UTILS_DATAPAIR_STRUCT));
			free(tmp_data);

			stream->memfile = memfile_init((char*)stream->playlist_mem.data, stream->playlist_mem.data_size);

			if (stream->memfile)
			{
				line = memfile_getnext_line(stream->memfile, 1);
				stream->playlist_position = 1;
				if (line)
				{
					if (strncmp(line, HLS_START_MARKER, strlen(HLS_START_MARKER)) == 0)
					{
						stream->state = HLS_STATE_LIVE;

						while (old_pos && stream->playlist_position < old_pos)
						{
							line = memfile_getnext_line(stream->memfile, 1);
							stream->playlist_position++;
						}

//						printf("[HLS] Going into loop\n");
						while(line)
						{
//							printf("Line: %s\n", line);
							if (strstr(line, HLS_SEGMENT_LEN_MARKER) != NULL)
							{
								HLS_SEGMENT * _seg = calloc(1, sizeof(HLS_SEGMENT)), *tmpseg = NULL;
								line += strlen(HLS_SEGMENT_LEN_MARKER);

								if (_seg)
								{
									double _total = 0;
									char *_dur = strtok(line, ",");
									char *_title = strtok(NULL, ",");
									char *_url = memfile_getnext_line(stream->memfile, 1);
//									printf("\tline2: %s\n", _url);
									stream->playlist_position++;

									_seg->duration = atof(_dur);
									_seg->url = strdup(_url);

									if (stream->active_iv_key)
									{
//										printf("HI\n");
										_seg->key = stream->active_iv_key;
										_seg->key->stream =  stream;
									}

									if (_title && strlen(_title) > 0)
										_seg->title = strdup(_title);

									_seg->parent = stream;

									if (!stream->first_segment)
									{
										_seg->prev = NULL;
										stream->first_segment = stream->curent_segment = _seg;
									}
									else
									{
										_seg->prev = stream->curent_segment;
										stream->curent_segment->next = _seg;
										stream->curent_segment = _seg;
									}


									tmpseg = stream->first_segment;
									_total = 0;

									while(tmpseg)
									{
										_total += _seg->duration;
										tmpseg = tmpseg->next;
									}

									_seg->timecode = stream->start_time + _total;
									_seg->stream_index = stream->index;
									_seg->index = stream->segment_count++;



//									printf("SegIDX: %" PRId64" -- %s (tc: %0.2f, I: %0.2f), start: %" PRId64"\n", _seg->index, _seg->url, _seg->timecode, _seg->duration, stream->start_time);
								}

							}
							else if (strstr(line, HLS_TARGET_DUR_MARKER) != NULL)
							{
								stream->ext_x_target_duration = atof(line+strlen(HLS_TARGET_DUR_MARKER));
//								printf("Target Dur: %0.2f\n", stream->ext_x_target_duration);
							}
							else if (strstr(line, HLS_DATETIME_MARKER) != NULL)
							{
								line += strlen(HLS_DATETIME_MARKER);
//								printf("1: %" PRId64"\n", utils_str_to_epoch2(line,0));
//								printf("2: %" PRId64"\n", utils_str_to_epoch2(line,1));
								if (!stream->start_time)
								{
									stream->start_time = utils_str_to_epoch2(line,1);
									printf("[MLB] Playlist start, datetime: %s (%" PRId64")\n", line, stream->start_time);
								}
							}
							else if (strstr(line, HLS_KEY_MARKER) != NULL)
							{
								uint8_t method = 0;
								char *uri=NULL, *iv=NULL;
								char *line2 = strtok(line+strlen(HLS_KEY_MARKER), ",");

								while(line2)
								{
//									printf("stuff: %s\n", line2);
									if (strstr(line2, HLS_KEY_METHOD_MARKER) != NULL)
									{
										line2 += strlen(HLS_KEY_METHOD_MARKER);
//										printf("Method: %s\n", line2);

										if (strncmp(line2, HLS_AES128_STR, strlen(HLS_AES128_STR)) == 0)
										{
//											printf("AES 128!!\n");
											method = HLS_KEY_TYPE_AES128;
										}
										else
										{
											printf("Unsupported keytype!!\n");
											method = HLS_KEY_TYPE_UNKNOWN;
										}
							 		}
									else if (strstr(line2, HLS_KEY_URI_MARKER) != NULL)
									{
										uri = line2+strlen(HLS_KEY_URI_MARKER);
									}
									else if (strstr(line2, HLS_KEY_IV_MARKER) != NULL)
									{
										iv = line2+strlen(HLS_KEY_IV_MARKER);
									}
									line2 = strtok(NULL, ",");
								}

								if (method && uri && iv)
								{
									uri++;
									uri[strlen(uri) -1] = '\0';
									iv += 2;
//									printf("Haz key stuff: %s %s\n", uri, iv);
									stream->iv_keys[stream->iv_keys_count].type = method;
									stream->iv_keys[stream->iv_keys_count].uri = strdup(uri);
									stream->iv_keys[stream->iv_keys_count].extra_data = (void*)master->url_params;
									str_to_bytes(iv, (unsigned char*)&stream->iv_keys[stream->iv_keys_count].iv, AES128_KEY_SIZE);
//									printf("URI: %s\n", stream->iv_keys[stream->iv_keys_count].uri);

/*
									printf("iv [%" PRId64"]: 0x", stream->iv_keys_count);
									for(i = 0; i < AES128_KEY_SIZE; i++)
									{
										printf("%02X", stream->iv_keys[stream->iv_keys_count].iv[i]);
									}
									printf("\n");
*/
									stream->active_iv_key = &stream->iv_keys[stream->iv_keys_count];
//									stream->active_iv_key->parent = stream->curent_segment;
									stream->iv_keys_count++;
									if (master->cb_process_ext_x_key)
										(*master->cb_process_ext_x_key)(stream);
//									stream->iv_keys[stream->iv_keys_count].iv = strdup(uri);
								}
							}
							else if (strstr(line, HLS_MEDIA_SEQ_MARKER) != NULL)
							{
								stream->ext_x_media_seq = atof(line+strlen(HLS_MEDIA_SEQ_MARKER));
//								printf("MediaSEQ: %0.2f\n", stream->ext_x_media_seq);
							}
							else if (strstr(line, HLS_VERSION_MARKER) != NULL)
							{
								stream->ext_x_version = atof(line+strlen(HLS_VERSION_MARKER));
//								printf("VErsion: %0.2f\n", stream->ext_x_version);
							}
							else if (strstr(line, HLS_END_MARKER) != NULL)
							{
								stream->state = HLS_STATE_END;
								printf("[HLS] End of playlist, stopping updates\n");
								break;
							}
							else
							{
								if (strstr(line, HLS_SEGMENT_LEN_MARKER) != NULL)
									printf("[MLB] Unhandled M3U8 line: %s\n", line);
							}
							line = memfile_getnext_line(stream->memfile, 1);

							if (line)
								stream->playlist_position++;
						}
//						printf("[HLS] Leaving loop: %d\n", stream->playlist_position);
					}
					else
					{
						printf("Skipping parse of playlist (already done)\n");
					}

				}
//				free(stream->memfile);
			}
			else
			{
				printf("NO MEMFILE\n");
			}
		}
		else
		{
			if (tmp_data)
			{
				if (tmp_data->data)
					free(tmp_data->data);
				free(tmp_data);
			}
			printf("[HLS] Not upating stream playlist, same size: %s\n", tmpstr);
//			printf("CURL Error: %d\n", res);
			return 1;
		}

	}
	return 0;
}

int _hls_process_master_playlist(HLS_MASTER_URL * master)
{
	if (master && master->list_mem.data_size)
	{
		TEXTFILE_IN_MEMORY *m = memfile_init((char*)master->list_mem.data, master->list_mem.data_size);
		if (m)
		{
			int i;
			char *line = NULL, *tmpc = NULL;
			master->stream_count = 0;
//			printf("_hls_process_master_playlist, setting streams to zero\n");
			for(i=0; i < HLS_MAX_STREAMS; i++)
				memset(&master->streams[i], 0, sizeof(HLS_STREAM_URL));

			line = memfile_getnext_line(m, 1);
			if (strncmp(line, HLS_START_MARKER, strlen(HLS_START_MARKER)) == 0)
			{
				while(line)
				{
					line = memfile_getnext_line(m, 1);
					if (line && strstr(line, HLS_STREAM_MARKER) == line)
					{
//						char *line2 = &line[strlen(HLS_STREAM_MARKER)];
						line = &line[strlen(HLS_STREAM_MARKER)];

						if (line)
						{
//							printf("Found Stream: %s\n", line);
							char *token = strtok(line, ",");
							while(token)
							{
								while(token[0] == ' ')
									token++;

								if (token)
								{
									tmpc = utils_split_value_after(token, '=');
									if (tmpc)
									{
										if (strstr(token, HLS_STREAM_PRGID_MARKER) == token)
										{
											master->streams[master->stream_count].program_id = atoi(tmpc);
										}
										else if (strstr(token, HLS_STREAM_BANDWIDTH_MARKER) == token)
										{
											master->streams[master->stream_count].bandwidth = (uint64_t)strtol(tmpc, (char **)NULL, 10);
										}
										free(tmpc);
										tmpc = NULL;
									}

								}
								token = strtok(NULL, ",");
							}
							line = memfile_getnext_line(m, 1);

//							snprintf(master->streams[master->stream_count].url, sizeof(master->streams[master->stream_count].url) - 1,  "%s", line);

							master->streams[master->stream_count].url = strdup(line);

							for(i=strlen(line); i > 0; i--)
							{
								if (line[i] == '/')
									break;
							}
							if (i > 0)
							{
								line[i] = '\0';
								master->streams[master->stream_count].base_url = strdup(line);
							}

							master->streams[master->stream_count].valid = 1;

							if (master->min_bandwidth && master->streams[master->stream_count].bandwidth < master->min_bandwidth)
								master->streams[master->stream_count].valid = 0;

							if (master->max_bandwidth && master->streams[master->stream_count].bandwidth > master->max_bandwidth)
								master->streams[master->stream_count].valid = 0;

							master->streams[master->stream_count].parent = master;
							master->stream_count++;
						}
					}
				}
			}
			else
			{
				printf("[HLS] Url does not start with: %s\n", HLS_START_MARKER);
			}
			free(m);
			// Sort the bitrates here
			if (master->stream_count)
			{
//				printf("Stream Count: %d\n",master->stream_count);
				for(i=0; i < master->stream_count-1; i++)
				{
					if (master->streams[i].bandwidth > master->streams[i+1].bandwidth)
					{
						_hls_swap_streams_in_list(&master->streams[i], &master->streams[i+1]);
						i = -1;
					}
				}
				for(i=0; i < master->stream_count; i++)
					master->streams[i].index = (uint8_t)i;
			}
			master->active_stream = &master->streams[master->stream_count-1];

			if (master->stream_count >= 2)
			{
				master->active_stream = &master->streams[(master->stream_count/2)-1];
			}
			printf("[HLS] Set initial stream to bandwidth: %" PRId64" (count: %d)\n", master->active_stream->bandwidth, master->stream_count);
		}
	}
	return 0;
}

int _hls_reload_master_url(HLS_MASTER_URL * master)
{
	if (master)
	{
		UTILS_DATAPAIR_STRUCT * tmp = NULL;
//		HLS_FETCH_OPTIONS opts = {0};

		if (master->list_mem.data)
		{
			free(master->list_mem.data);
			master->list_mem.data = NULL;
		}
		master->list_mem.data_size = 0;
		printf("_hls_reload_master_url: %s\n",master->master_url);
		tmp = hls_curl_get(master->master_url, &master->playlist_opts);
		if (tmp && tmp->data && tmp->data_size)
			memcpy(&master->list_mem, tmp, sizeof(UTILS_DATAPAIR_STRUCT));
	}
	return 0;
}

void _hls_key_destroy(HLS_KEY *key)
{
	if (key)
	{
		if (key->uri)
			free(key->uri);
	}
	return;
}

void _hls_segment_destroy(HLS_SEGMENT *segment)
{
	if (segment)
	{
		if (segment->url)
			free(segment->url);

		if (segment->title)
			free(segment->title);
	}
	return;
}


void _hls_stream_destroy(HLS_STREAM_URL *stream)
{
	if (stream)
	{
		uint64_t i;
		HLS_SEGMENT *seg = stream->first_segment, *del = NULL;
		HLS_MASTER_URL * master = stream->parent;

		if (master->playlist_opts.handle)
		{
			curl_easy_cleanup(master->playlist_opts.handle);
			master->playlist_opts.handle = NULL;
		}
		while(seg)
		{
			_hls_segment_destroy(seg);
			del = seg;
			seg = seg->next;
			free(del);
		}

		for(i=0; i < stream->iv_keys_count; i++)
		{
			_hls_key_destroy(&stream->iv_keys[i]);
		}

		if (stream->url)
			free(stream->url);

		if (stream->base_url)
			free(stream->base_url);

		if (stream->playlist_mem.data)
			free(stream->playlist_mem.data);

	}
	return;
}

void hls_destroy(HLS_MASTER_URL *master)
{
	if (master)
	{
		int i;
		if (master->playlist_opts.handle)
		{
			curl_easy_cleanup(master->playlist_opts.handle);
			master->playlist_opts.handle = NULL;
		}
		if (master->master_url)
			free(master->master_url);

		if (master->base_url)
			free(master->base_url);

		if (master->url_params)
			free(master->url_params);

		for(i=0; i < master->stream_count; i++)
			_hls_stream_destroy(&master->streams[i]);

		if (master->decrypt_mem);
			free(master->decrypt_mem);
		printf("[HLS] Destroy\n");
	}
	return;
}



HLS_MASTER_URL * hls_init(char *url)
{
	HLS_MASTER_URL *ret = NULL;
	if (url)
	{
		int i;
		ret = calloc(1, sizeof(HLS_MASTER_URL));
		if (!ret->playlist_opts.handle)
		{
			curl_global_init(CURL_GLOBAL_ALL);
			ret->playlist_opts.handle = curl_easy_init();
		}

		ret->master_url = strdup(url);
		for(i=strlen(ret->master_url); i > 0; i--)
		{
			if (ret->master_url[i-1] == '/')
				break;
		}

		ret->decrypt_mem_size = HLS_DEFAULT_BUFSIZE;
		ret->decrypt_mem =  malloc(ret->decrypt_mem_size);
		ret->base_url = calloc(1, i+1);
		memcpy(ret->base_url, ret->master_url, i-1);

		_hls_reload_master_url(ret);
		_hls_process_master_playlist(ret);

	}
	return ret;
}


void hls_curl_set_opts(CURL *ch, HLS_FETCH_OPTIONS *opts)
{
	if (ch && opts)
	{
		if (!opts->handle)
			opts->handle = ch;


		if (opts->cookie_file)
		{
			curl_easy_setopt(ch, CURLOPT_COOKIEFILE, opts->cookie_file);
//			printf("hls, setting cookiefile: %s\n", opts->cookie_file);
		}

		if (opts->cookie_jar)
		{
			curl_easy_setopt(ch, CURLOPT_COOKIEJAR, opts->cookie_jar);
//			printf("hls, setting cookiejar: %s\n", opts->cookie_file);
		}

		if (opts->errbuf)
			curl_easy_setopt(ch, CURLOPT_ERRORBUFFER, opts->errbuf);

		if (opts->user_agent)
			curl_easy_setopt(ch, CURLOPT_USERAGENT, opts->user_agent);

		if (opts->connect_timeout)
			curl_easy_setopt(ch, CURLOPT_CONNECTTIMEOUT,opts->connect_timeout);

		if (opts->timeout)
			curl_easy_setopt(ch, CURLOPT_TIMEOUT, opts->timeout);

		if (opts->buffer_size)
			curl_easy_setopt(ch, CURLOPT_BUFFERSIZE, opts->buffer_size);

		if (opts->dns_cache_timeout)
			curl_easy_setopt(ch, CURLOPT_DNS_CACHE_TIMEOUT, opts->dns_cache_timeout);

		if (opts->fail_on_error)
			curl_easy_setopt(ch, CURLOPT_FAILONERROR, opts->fail_on_error);

		if (opts->proxy)
		{
			switch(opts->proxy_type)
			{
				case HLS_PROXY_TYPE_HTTP:
					curl_easy_setopt(ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
				break;

				case HLS_PROXY_TYPE_SOCKS:
					curl_easy_setopt(ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
				break;
			}
		}
	}
}

UTILS_DATAPAIR_STRUCT * hls_curl_post(char *url, char *pdata, HLS_FETCH_OPTIONS *opts)
{
	UTILS_DATAPAIR_STRUCT *ret = NULL;
	if (url && pdata)
	{
		ret = calloc(1, sizeof(UTILS_DATAPAIR_STRUCT));
		CURL *ch  = NULL;
		CURLcode res;

		if (opts)
		{
			ch = opts->handle;
			ret->throw_away = opts->throw_away;
		}

		if (!ch)
			ch = curl_easy_init();

		curl_easy_setopt(ch, CURLOPT_URL, url);
		hls_curl_set_opts(ch, opts);
		curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, generic_curl_handler);
		curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void*)ret);
		curl_easy_setopt(ch, CURLOPT_POSTFIELDS, pdata);
		res = curl_easy_perform(ch);

		if (res == CURLE_OK)
		{
			return ret;
		}
		else
		{
			if (ret->data)
				free(ret->data);
			free(ret);
			return NULL;
		}
	}
	return ret;
}

UTILS_DATAPAIR_STRUCT * hls_curl_get(char *url, HLS_FETCH_OPTIONS *opts)
{
	UTILS_DATAPAIR_STRUCT *ret = NULL;
	if (url)
	{
		ret = calloc(1, sizeof(UTILS_DATAPAIR_STRUCT));
		CURL *ch  = NULL;
		CURLcode res;

		if (opts)
		{
			ch = opts->handle;
			ret->throw_away = opts->throw_away;
		}

		if (!ch)
			ch = curl_easy_init();
//		else
//			curl_easy_reset(ch);
		curl_easy_setopt(ch, CURLOPT_URL, url);
		hls_curl_set_opts(ch, opts);
		curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, generic_curl_handler);
		curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void*)ret);
		res = curl_easy_perform(ch);

		if (res == CURLE_OK)
		{
			return ret;
		}
		else
		{
			if (ret->data)
				free(ret->data);
			free(ret);
			return NULL;
		}
	}
	return ret;
}


#ifdef _MAIN_

int main (int argc, char *argv[])
{
//	HLS_MASTER_URL *m = hls_init("http://mlblive-akc.mlb.com/ls04/mlbam/2014/03/01/MLB_GAME_VIDEO_MIANYN_HOME_20140301/master_wired.m3u8");
	HLS_MASTER_URL *m = hls_init("http://mlblive-l3c.mlb.com/ls04/mlbam/2014/03/07/MLB_GAME_VIDEO_SLNNYN_HOME_20140307/master_wired.m3u8");
//	HLS_MASTER_URL *m = hls_init("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8");

	if (m)
	{
		hls_print_stream_info(m);

		m->playlist_opts.user_agent  = CURL_USER_AGENT_STR;
		m->segment_opts.user_agent  = CURL_USER_AGENT_STR;
		hls_update_stream_playlist(m);

		if (m->active_stream)
		{
			HLS_STREAM_URL * stream = m->active_stream;
//			HLS_SEGMENT *_seg = stream->first_segment;
			printf("Haz Active: %" PRId64"\n" , stream->bandwidth);

			hls_stream_fetch_decrypt(stream);
/*
			while(_seg)
			{
				printf("Duration: %0.2f, Title: %s, URI: %s .. ",  _seg->duration, _seg->title, _seg->url);
				if ( _seg->key)
				{
					printf("Key: %s, IV: 0x",  _seg->key->uri);
					for(i = 0; i < AES128_KEY_SIZE; i++)
					{
						printf("%02X", _seg->key->iv[i]);
					}
				}
				printf("\n");
				_hls_fetch_segment(_seg);
				_seg = _seg->next;
			}
*/

		}
		printf("Exiting...\n");
		hls_destroy(m);

	}
	return 0;
}

#endif

